package tk.kolesnikov.algorithms;

/**
 * Created by seko0313 on 17.01.14.
 */
public class DCTHelper {

    /*
     * Method that calculate dct2 in two dimensions directly
     * WORKS SLOW!!!
     */
    public static double[][] dct2in2dimension(double[][] matrix, double offset) {
        if (matrix.length == 0 || matrix[0].length == 0)
            throw new IllegalStateException("empty matrix");

        int n = matrix.length;
        int m = matrix[0].length;

        double[][] result = new double[n][m];
        double[] alf1 = initDct(n);
        double[] alf2 = initDct(m);

        double sum;
        for (int k = 0; k < n; k++) {
            for (int l = 0; l < m; l++) {
                sum = 0;
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < m; j++) {
                        sum += (matrix[i][j] + offset)
                                * Math.cos((Math.PI * (2 * i + 1) * k)
                                / (2 * n))
                                * Math.cos((Math.PI * (2 * j + 1) * l)
                                / (2 * m));
                    }
                }
                result[k][l] = alf1[k] * alf2[l] * sum;
                //System.out.println(k + " " + l + ": " + sum + "*" + alf1[k]
                //        + "*" + alf2[l] + " -> " + c[k][l]);
            }
        }

        return result;
    }

    /*
     * Method that calculate idct2 in two dimensions directly
     * just as described here:
     * http://www.mathworks.it/help/toolbox/images/ref/idct2.html
     * WORKS SLOW!!!
     */
    public static double[][] idct2in2dimension(double[][] matrix, double offset) {
        if (matrix.length == 0 || matrix[0].length == 0)
            throw new IllegalStateException("empty matrix");

        int n = matrix.length;
        int m = matrix[0].length;

        double[][] result = new double[n][m];
        double[] alf1 = initDct(n);
        double[] alf2 = initDct(m);

        for (int k = 0; k < n; k++) {
            for (int l = 0; l < m; l++) {
                result[k][l] = 0;
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < m; j++) {
                        result[k][l] += alf1[i]
                                * alf2[j]
                                * matrix[i][j]
                                * Math.cos((Math.PI * (2 * k + 1) * i)
                                / (2 * n))
                                * Math.cos((Math.PI * (2 * l + 1) * j)
                                / (2 * m));
                    }
                }
                result[k][l] += offset;
                //System.out.println(k + " " + l + ": " + c[k][l]);
            }
        }

        return result;
    }

    /*
     * Method that calculate dct2 in two dimensions, first
     * calculate dct in row and after calculate dct in column
     * WORKS FAST
     */
    public static double[][] dct2(double[][] matrix, double offset) {
        if (matrix.length == 0 || matrix[0].length == 0)
            throw new IllegalStateException("empty z parameter");

        int n = matrix.length;
        int m = matrix[0].length;
        double[][] c = new double[n][m];
        double[][] c2 = new double[n][m];
        double alfa;
        double sum;

        for (int k = 0; k < n; k++) {
            for (int l = 0; l < m; l++) {
                sum = 0;
                for (int i = 0; i < n; i++) {
                    sum += (matrix[i][l] + offset)
                            * Math.cos((Math.PI * (2. * i + 1.) * k) / (2. * n));
                }
                alfa = k == 0 ? 1. / Math.sqrt(n) : Math.sqrt(2. / n);
                c[k][l] = alfa * sum;
            }
        }

        for (int l = 0; l < m; l++) {
            for (int k = 0; k < n; k++) {
                sum = 0;
                for (int j = 0; j < m; j++) {
                    sum += c[k][j]
                            * Math.cos((Math.PI * (2. * j + 1.) * l) / (2. * m));
                }
                alfa = l == 0 ? 1. / Math.sqrt(m) : Math.sqrt(2. / m);
                c2[k][l] = alfa * sum;
            }
        }

        return c2;
    }

    /*
     * Method that calculate idct2 in two dimensions, first
     * calculate idct in row and after calculate idct in column
     * WORKS FAST
     */
    public static double[][] idct2(double[][] matrix, double offset) {
        if (matrix.length == 0 || matrix[0].length == 0)
            throw new IllegalStateException("empty z parameter");

        int n = matrix.length;
        int m = matrix[0].length;
        double[][] c = new double[n][m];
        double[][] c2 = new double[n][m];
        double alfa;

        for (int k = 0; k < n; k++) {
            for (int l = 0; l < m; l++) {
                c[k][l] = 0;
                for (int i = 0; i < n; i++) {
                    alfa = i == 0 ? 1. / Math.sqrt(n) : Math.sqrt(2. / n);
                    c[k][l] += alfa * matrix[i][l]
                            * Math.cos((Math.PI * (2 * k + 1) * i) / (2 * n));
                }
            }
        }

        for (int l = 0; l < m; l++) {
            for (int k = 0; k < n; k++) {
                c2[k][l] = 0;
                for (int j = 0; j < m; j++) {
                    alfa = j == 0 ? 1. / Math.sqrt(m) : Math.sqrt(2. / m);
                    c2[k][l] += alfa * c[k][j]
                            * Math.cos((Math.PI * (2 * l + 1) * j) / (2 * m));
                }
                c2[k][l] += offset;
            }
        }

        return c2;
    }

    private static double[] initDct(int n) {
        double[] alf = new double[n];
        alf[0] = 1. / Math.sqrt(n);
        for (int k = 1; k < n; k++)
            alf[k] = Math.sqrt(2. / n);
        return alf;
    }

}
